import ELayout from "../enums/ELayout";
import Vector2 from "../maths/Vector2";
import Size from "../geom/Size";
import Rectangle from "../geom/Rectangle";

type TextAlgin = 'start' | 'left' | 'center' | 'right' | 'end';

type TextBaseline = 'alphabetic' | 'hanging' | 'top' | 'middle' | 'bottom';

type FontType = '10px sans-serif' | '15px sans-serif' | '20px sans-serif' | '25 sans-serif';

type PatternRepeat = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';

type FontStyle = "normal" | "italic" | "oblique";

type FontVariant = "normal" | "small-caps";

type FontWeight = "normal" | "bold" | "bolder" | "lighter" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900";

type FontSize = "10px" | "12px" | "16px" | "18px" | "24px" | "50%" | "75%" | "100%" | "125%" | "150%" | "xx-small" | "x-small" | "small" | "medium" | "large" | "x-large" | "xx-large";

type FontFamily = "sans-serif" | "serif" | "courier" | "fantasy" | "monospace";

export default class Graphic {
    private _canvas: HTMLCanvasElement;
    private _context2D: CanvasRenderingContext2D;

    public constructor(canvas: HTMLCanvasElement) {
        this._canvas = canvas;
        this._context2D = canvas.getContext('2d') as CanvasRenderingContext2D;
    }

    public strokeLine(x0: number, y0: number, x1: number, y1: number): void {
        if (this._context2D !== null) {
            this._context2D.save();
            // this._context2D.strokeStyle = 'red';
            this._context2D.beginPath();
            this._context2D.moveTo(x0, y0);
            this._context2D.lineTo(x1, y1);
            this._context2D.stroke();
            this._context2D.closePath();
            this._context2D.restore();
        }
    }

    public fillCircle(x: number, y: number, radius: number, fillStyle: string | CanvasGradient | CanvasPattern = 'red'): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.fillStyle = fillStyle;
            this._context2D.beginPath();
            this._context2D.arc(x, y, radius, 0, Math.PI * 2);
            this._context2D.fill();
            this._context2D.restore();
        }
    }

    public fillText(text: string, x: number, y: number, color: string = 'white', align: TextAlgin = 'left', baseline: TextBaseline = 'top', font: FontType = '10px sans-serif'): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.textAlign = align;
            this._context2D.textBaseline = baseline;
            this._context2D.font = font;
            this._context2D.fillStyle = color;
            this._context2D.fillText(text, x, y);
            this._context2D.restore();
        }
    }

    /**
     * *计算文本的宽高
     * @param text 
     * @param char 
     * @param scale 
     */
    public calcTextSize(text: string, char: string = 'W', scale: number = 0.5): Size {
        if (this._context2D !== null) {
            let size: Size = new Size();
            size.width = this._context2D.measureText(text).width;
            let w: number = this._context2D.measureText(char).width;
            size.height = w + w * scale;
            return size;
        }
        alert('context2D渲染上下文为null');
        throw new Error('context2D渲染上下文为null');
    }
    /**
     * 
     * @param layout 
     * @param text 
     * @param parentWidth 
     * @param parentHeight 
     */
    public calcLocalTextRectangle(layout: ELayout, text: string, parentWidth: number, parentHeight: number): Rectangle {
        // 计算文本的大小
        let s: Size = this.calcTextSize(text);
        // 创建一个三维向量
        let o: Vector2 = Vector2.create();
        // 计算出当前文本子矩形左上角相对父矩形空间中的3个关键点（左上、中心、右下）坐标
        // 1.当前文本子矩形左上角相对父矩形左上角坐标，由于局部表示，所以为[0,0]
        let left: number = 0;
        let top: number = 0;
        // 2.当前文本子矩形左上角相对父矩形右下角坐标
        let right: number = parentWidth - s.width;
        let bottom: number = parentHeight - s.height;
        // 3.当前文本子矩形左上角相对父矩形中心点坐标
        let center: number = right * 0.5;
        let middle: number = bottom * 0.5;
        switch (layout) {
            case ELayout.LEFT_TOP:
                o.x = left;
                o.y = top;
                break;
            case ELayout.RIGHT_TOP:
                o.x = right;
                o.y = top;
                break;
            case ELayout.RIGHT_BOTTOM:
                o.x = right;
                o.y = bottom;
                break;
            case ELayout.LEFT_BOTTOM:
                o.x = left;
                o.y = bottom;
                break;
            case ELayout.CENTER_MIDDLE:
                o.x = center;
                o.y = middle;
                break;
            case ELayout.CENTER_TOP:
                o.x = center;
                o.y = top;
                break;
            case ELayout.RIGHT_MIDDLE:
                o.x = right;
                o.y = middle;
                break;
            case ELayout.CENTER_BOTTOM:
                o.x = center;
                o.y = bottom;
                break;
            case ELayout.LEFT_MIDDLE:
                o.x = left;
                o.y = middle;
                break;
        }
        return new Rectangle(o, s);
    }

    public fillRectWithTitle(x: number, y: number, width: number, height: number, title: string = '', layout: ELayout = ELayout.CENTER_MIDDLE, color: string = 'grey', showCoord: boolean = true): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.fillStyle = color;
            this._context2D.beginPath();
            this._context2D.rect(x, y, width, height);
            this._context2D.fill();
            // 如果有文字，根据枚举值计算x、y坐标
            if (title.length !== 0) {
                let rect: Rectangle = this.calcLocalTextRectangle(layout, title, width, height);
                this.fillText(title, x + rect.origin.x, y + rect.origin.y, 'white', 'left', 'top', '10px sans-serif');
                this.strokeRect(x + rect.origin.x, y + rect.origin.y, rect.size.width, rect.size.height, 'rgba( 0 , 0 , 0 , 0.5 ) ');
                this.fillCircle(x + rect.origin.x, y + rect.origin.y, 2);
            }
            if (showCoord) {
                this.strokeCoord(x, y, width + 20, height + 20);
                this.fillCircle(x, y, 3);
            }
            this._context2D.restore();
        }
    }

    public fillLocalRectWithTitle(width: number, height: number, title: string = '', referencePt: ELayout = ELayout.LEFT_TOP, layout: ELayout = ELayout.CENTER_MIDDLE, color: string = 'grey', showCoord: boolean = false): void {
        if (this._context2D !== null) {
            let x: number = 0;
            let y: number = 0;
            // 首先根据referencePt的值计算原点相对左上角的偏移量
            // Canvas2D中，左上角是默认的坐标系原点，所有原点变换都是相对左上角的偏移
            switch (referencePt) {
                case ELayout.LEFT_TOP:  // Canvas2D中，默认是左上角为坐标系原点
                    x = 0;
                    y = 0;
                    break;
                case ELayout.LEFT_MIDDLE:   // 左中为原点
                    x = 0;
                    y = -height * 0.5;
                    break;
                case ELayout.LEFT_BOTTOM:   // 左下为原点
                    x = 0;
                    y = -height;
                    break;
                case ELayout.RIGHT_TOP:     // 右上为原点
                    x = -width;
                    y = 0;
                    break;
                case ELayout.RIGHT_MIDDLE:  // 右中为原点
                    x = -width;
                    y = -height * 0.5;
                    break;
                case ELayout.RIGHT_BOTTOM:  // 右下为原点
                    x = -width;
                    y = -height;
                    break;
                case ELayout.CENTER_TOP:    // 中上为原点
                    x = -width * 0.5;
                    y = 0;
                    break;
                case ELayout.CENTER_MIDDLE: // 中中为原点
                    x = -width * 0.5;
                    y = -height * 0.5;
                    break;
                case ELayout.CENTER_BOTTOM: // 中下为原点
                    x = -width * 0.5;
                    y = -height;
                    break;
            }
            // 下面的代码和上一章实现的fillRectWithTitle一样
            this._context2D.save();
            // 1.绘制矩形
            this._context2D.fillStyle = color;
            this._context2D.beginPath();
            this._context2D.rect(x, y, width, height);
            this._context2D.fill();
            // 如果有文字，先根据枚举值计算x,y坐标
            if (title.length !== 0) {
                // 2.绘制文字信息
                // 在矩形的左上角绘制出相关文字信息,使用的是10px大小的文字
                let rect: Rectangle = this.calcLocalTextRectangle(layout, title, width, height);
                // 绘制文本
                this.fillText(title, x + rect.origin.x, y + rect.origin.y, 'white', 'left', 'top');
                // 绘制文本框
                this.strokeRect(x + rect.origin.x, y + rect.origin.y, rect.size.width, rect.size.height, 'rgba(0,0,0,0.5)');
                // 绘制文本框左上角坐标(相对父矩形表示)
                this.fillCircle(x + rect.origin.x, y + rect.origin.y, 2);
            }
            // 3.绘制变换的局部坐标系,局部坐标系原点总是为[0,0]
            // 附加一个坐标,x轴和y轴比矩形的width和height多20像素,并且绘制3像素的原点
            if (showCoord) {
                this.strokeCoord(0, 0, width + 20, height + 20);
                this.fillCircle(0, 0, 3);
            }
            this._context2D.restore();
        }
    }

    /**
     * 
     * @param width     矩形的宽度
     * @param height    矩形的高度
     * @param title     矩形显示的文本内容
     * @param u         这里使用u和v参数代替原来的ELayout枚举
     * @param v 
     * @param layout    文字框对齐的方式
     * @param color     矩形填充的颜色
     * @param showCoord     是否显示局部坐标系，默认显示
     */
    public fillLocalRectWithTitleUV(width: number, height: number, title: string = '', u: number = 0, v: number = 0, layout: ELayout = ELayout.CENTER_MIDDLE, color: string = 'grey', showCoord: boolean = true): void {
        if (this._context2D !== null) {
            // 将原来的fillLocalRectWithTitle方法中的ELayout9种处理方式的代码替换成如下代码
            let x: number = -width * u;
            let y: number = -height * v;
            // 和fillLocalRectWithTile中的绘制代码一样
            this._context2D.save();
            // 1.绘制矩形
            this._context2D.fillStyle = color;
            this._context2D.beginPath();
            this._context2D.rect(x, y, width, height);
            this._context2D.fill();
            // 如果有文字，先根据枚举值计算x,y坐标
            if (title.length !== 0) {
                // 2.绘制文字信息
                // 在矩形的左上角绘制出相关文字信息,使用的是10px大小的文字
                let rect: Rectangle = this.calcLocalTextRectangle(layout, title, width, height);
                // 绘制文本
                this.fillText(title, x + rect.origin.x, y + rect.origin.y, 'white', 'left', 'top');
                // 绘制文本框
                this.strokeRect(x + rect.origin.x, y + rect.origin.y, rect.size.width, rect.size.height, 'rgba(0,0,0,0.5)');
                // 绘制文本框左上角坐标(相对父矩形表示)
                this.fillCircle(x + rect.origin.x, y + rect.origin.y, 2);
            }
            // 3.绘制变换的局部坐标系,局部坐标系原点总是为[0,0]
            // 附加一个坐标,x轴和y轴比矩形的width和height多20像素,并且绘制3像素的原点
            if (showCoord) {
                this.strokeCoord(0, 0, width + 20, height + 20);
                this.fillCircle(0, 0, 3);
            }
            this._context2D.restore();
        }
    }

    /**
     * *绘制网格
     * @param color     网格颜色
     * @param interval  网格大小
     */
    public strokeGrid(color: string = 'grey', interval: number = 10): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.strokeStyle = color;
            this._context2D.lineWidth = 0.5;
            for (let i: number = interval + 0.5; i < this._canvas.width; i += interval) {
                this.strokeLine(i, 0, i, this._canvas.height);
            }
            for (let i: number = interval + 0.5; i < this._canvas.height; i += interval) {
                this.strokeLine(0, i, this._canvas.width, i);
            }
            this._context2D.restore();
        }
        this.fillCircle(0, 0, 5, 'green');
        this.strokeCoord(0, 0, this._canvas.width, this._canvas.height);
    }

    public strokeCoord(orginX: number, orginY: number, width: number, height: number): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.strokeStyle = 'red';
            this.strokeLine(orginX, orginY, orginX + width, orginY);
            this._context2D.restore();

            this._context2D.save();
            this._context2D.strokeStyle = 'blue';
            this.strokeLine(orginX, orginY, orginX, orginY + height);
            this._context2D.restore();
        }
    }

    public strokeLocalCoord(width: number, height: number, lineWidth: number = 1): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.lineWidth = lineWidth;
            this._context2D.strokeStyle = 'red';
            this.strokeLine(0, 0, width, 0);
            this._context2D.strokeStyle = 'blue';
            this.strokeLine(0, 0, 0, height);
            this._context2D.restore();
        }
    }

    public strokeCircle(x: number, y: number, radius: number, color: string = 'red', lineWidth: number = 1): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.strokeStyle = color;
            this._context2D.lineWidth = lineWidth;
            this._context2D.beginPath();
            this._context2D.arc(x, y, radius, 0, Math.PI * 2);
            this._context2D.stroke();
            this._context2D.restore();
        }
    }

    public strokeRect(x: number, y: number, w: number, h: number, color: string = 'black'): void {
        if (this._context2D !== null) {
            this._context2D.save();
            this._context2D.strokeStyle = color;
            this._context2D.beginPath();
            this._context2D.moveTo(x, y);
            this._context2D.lineTo(x + w, y);
            this._context2D.lineTo(x + w, y + h);
            this._context2D.lineTo(x, y + h);
            this._context2D.closePath();
            this._context2D.stroke();
            this._context2D.restore();
        }
    }
}